/// Author: QP
/// Version: 1.0
/// Date:2022/11
/// FHIR服务器业务操作，用于向FHIR服务器修正和提交Bundle
Class HCC.FHIR.ServerOperation Extends Ens.BusinessOperation
{

Parameter INVOCATION = "Queue";

Parameter SETTINGS = "FHIRServerOperation,FHIRServerEndPoint,Debug,DebugFilePath";

Property FHIRServerOperation As Ens.DataType.ConfigName [ InitialExpression = "HS.FHIRServer.Interop.HTTPOperation" ];

Property FHIRServerEndPoint As %String;

/// 是否调试模式。调试模式下会将中间产生的SDA和FHIR输出到DebugFilePath目录下
Property Debug As %Boolean [ InitialExpression = 0 ];

/// 调试模式下，中间文件产生路径
Property DebugFilePath As %String;

/// revise resource reference URL
Method ReviseAndUpload(pRequest As Ens.StreamContainer, Output pResponse As Ens.StringResponse) As %Status
{
	Set tSC = $$$OK

	Try 
	{		
		Set pResponse=##class(Ens.StringResponse).%New()
		k ^||Loc,^||Pra,^||Org

		Set (tPatientUrl, tEncounterUrl,tOrgUrl,tLocUrl,tPraUrl)=""
		
		Set tSourceJSON = {}.%FromJSON(pRequest.Stream)
			
		// quit if there is no data
		If ('$ISOBJECT(tSourceJSON.entry)) || (tSourceJSON.entry.%Size()=0) 
		{
			Set tSC = $$$ERROR(-10001,"Incorrect FHIR data in JSON format")
			Quit
		}
		Set tSize = tSourceJSON.entry.%Size()
		
		// initiate target JSON object
		Set tTargetJSON = {}.%New()
		Set tTargetJSON.resourceType = "Bundle"
		Set tTargetJSON.type = "transaction"
		Set tTargetJSON.entry=[]
		
		// loop on source JSON object
		For i=1:1:tSize
		{
			Set tJObj = tSourceJSON.entry.%Get(i-1)
			If tJObj.request.url="Patient"
			{
				continue:('$ISOBJECT(tJObj.resource.identifier))
				For j=1:1:tJObj.resource.identifier.%Size()
				{
					If tJObj.resource.identifier.%Get(j-1).type.text="SSN"
					{
						Set tPatientUrl = ..GetPatientUrl(tJObj.resource.identifier.%Get(j-1).value)
					}
					Else
					{
						if tJObj.resource.identifier.%Get(j-1).assigner.reference'=""
						{
							Set tRefLink = $G(^||Org(tJObj.resource.identifier.%Get(j-1).assigner.reference))
							Set:(tRefLink'="") tJObj.resource.identifier.%Get(j-1).assigner.reference = tRefLink
						}
					}
				}
				// if patient does not exist, push it into list
				If tPatientUrl=""
				{
					If tJObj.resource.managingOrganization.reference '=""
					{
						Set tRefLink = $G(^||Org(tJObj.resource.managingOrganization.reference))
						Set:(tRefLink'="") tJObj.resource.managingOrganization.reference = tRefLink
					}
					Do tTargetJSON.entry.%Push(tJObj)
				}
			}
			// find out encounter url, if it exists already
			ElseIf tJObj.request.url="Encounter"
			{
				continue:('$ISOBJECT(tJObj.resource.identifier))
				
				For j=1:1:tJObj.resource.identifier.%Size()
				{
					If tJObj.resource.identifier.%Get(j-1).type.text="EncounterNumber"
					{
						Set tEncounterUrl = ..GetEncounterUrl(tJObj.resource.identifier.%Get(j-1).value)
						// quit this loop
						Quit
					}
				}
				// if encounter exists already, no process
				If tEncounterUrl=""
				{
					Set:((tJObj.resource.subject.reference '="")&&(tPatientUrl'="")) tJObj.resource.subject.reference = tPatientUrl
					// replace serviceProvider with existing Organization URL
					If (tJObj.resource.serviceProvider.reference '="")
					{
						Set tRefLink = $G(^||Org(tJObj.resource.serviceProvider.reference))
						Set:(tRefLink'="") tJObj.resource.serviceProvider.reference = tRefLink
					} 
					// replace location with existing Location URL (QP revised on 2022/8/15)
					If $ISOBJECT(tJObj.resource.location)
					{
						For j=1:1:tJObj.resource.location.%Size()
						{
							Set tLocRef = tJObj.resource.location.%Get(j-1).location.reference
							Set tRefLink = $G(^||Loc(tLocRef))
							Set:(tRefLink'="") tJObj.resource.location.%Get(j-1).location.reference = tRefLink
						} 
					}
					Do tTargetJSON.entry.%Push(tJObj)
				}
			}
			// find out organization url, if it exists already
			ElseIf tJObj.request.url="Organization"
			{
				Set tFullURL = tJObj.fullUrl,tOrgUrl=""
				continue:('$ISOBJECT(tJObj.resource.identifier)||(tFullURL=""))
				For j=1:1:tJObj.resource.identifier.%Size()
				{
					Set tOrgUrl = ..GetOrganizationUrl(tJObj.resource.identifier.%Get(j-1).value)
					// quit this loop
					If (tOrgUrl'="")
					{
						Set ^||Org(tFullURL)=tOrgUrl
						Quit
					}
				}
				// if organization does not exist, push it into list
				Do:(tOrgUrl="") tTargetJSON.entry.%Push(tJObj)
			}
			// find out Location url, if it exists already
			ElseIf tJObj.request.url="Location"
			{
				Set tFullURL = tJObj.fullUrl,tLocUrl=""
				continue:('$ISOBJECT(tJObj.resource.identifier)||(tFullURL=""))
				For j=1:1:tJObj.resource.identifier.%Size()
				{
					Set tLocUrl = ..GetLocationUrl(tJObj.resource.identifier.%Get(j-1).value)
					// quit this loop
					If (tLocUrl'="")
					{
						Set ^||Loc(tFullURL)=tLocUrl
						Quit
					}
				}
				// if Location exists already, no process
				Do:(tLocUrl="") tTargetJSON.entry.%Push(tJObj)
			}
			// find out Practitioner url, if it exists already
			ElseIf tJObj.request.url="Practitioner"
			{
				Set tFullURL = tJObj.fullUrl,tPraUrl=""
				continue:('$ISOBJECT(tJObj.resource.identifier) || (tFullURL=""))
				For j=1:1:tJObj.resource.identifier.%Size()
				{
					Set tPraUrl = ..GetPractitionerUrl(tJObj.resource.identifier.%Get(j-1).value)
					// quit this loop
					If (tPraUrl'="")
					{
						Set ^||Pra(tFullURL)=tPraUrl
						Quit
					}
				}
				// if Location exists already, no process
				Do:(tPraUrl="") tTargetJSON.entry.%Push(tJObj)
			}
			// for others, just modify their subject and encounter reference
			Else
			{
				Set:((tJObj.resource.encounter.reference '="")&&(tEncounterUrl'="")) tJObj.resource.encounter.reference = tEncounterUrl
				Set:((tJObj.resource.context.reference '="")&&(tEncounterUrl'="")) tJObj.resource.context.reference = tEncounterUrl
				
				Set:((tJObj.resource.subject.reference '="")&&(tPatientUrl'="")) tJObj.resource.subject.reference = tPatientUrl
				Set:((tJObj.resource.patient.reference '="")&&(tPatientUrl'="")) tJObj.resource.patient.reference = tPatientUrl
				
				if tJObj.resource.performer.actor.reference '=""
				{
					Set tRefLink = $G(^||Pra(tJObj.resource.performer.actor.reference))
					Set:(tRefLink'="") tJObj.resource.performer.actor.reference = tRefLink
				}
				Do tTargetJSON.entry.%Push(tJObj)
			}
		}
			
		// write data and save target stream	
		Set tQuickStream=##class(HS.SDA3.QuickStream).%New()
		Do tTargetJSON.%ToJSON(tQuickStream)
		Do tQuickStream.%Save()
		Set tStreamId = tQuickStream.%Id()
		
		// store revised FHIR for debug purpose
		if ..Debug=1
		{
			Set tFHIRFile=##class(%Stream.FileCharacter).%New()
			Set tSC=tFHIRFile.LinkToFile(..DebugFilePath_$Case($system.Version.GetOS(),"Windows":"\",:"/")_$SYSTEM.Util.CreateGUID()_"-revised.JSON")
			Set tFHIRFile.TranslateTable="UTF8"
			Do tFHIRFile.CopyFrom(tQuickStream)
			D tFHIRFile.%Save(),tFHIRFile.%Close()
			K tFHIRFile
		}

		// must to kill, otherwise, post operation will not able to open it!
		Do tQuickStream.%Close()
		k tQuickStream
		
		// clean up
		Do tJObj.%Close(),tSourceJSON.%Close(),tTargetJSON.%Close()
		k ^||Loc,^||Pra,^||Org
			
		// post to fhir server
		Set pResponse.StringValue = tStreamId_":"_ ..PostBundle(tStreamId)

		// clean up stream (added by QP on 2023/05/23)
		Do ##class(HS.SDA3.QuickStream).%DeleteId(tStreamId)
	}
	Catch ex 
	{
		Set tSC=ex.AsStatus()
	}
	Quit tSC
}

/// post bundle to FHIR server
Method PostBundle(pStreamId) As %String
{
	Set tRequest = ##class(HS.FHIRServer.Interop.Request).%New()
	Set tResponse = ##class(HS.FHIRServer.Interop.Response).%New()
	Set tRequest.Request.BaseURL=..FHIRServerEndPoint
	Set tRequest.QuickStreamId = pStreamId
	Set tRequest.Request.RequestMethod="POST"
	Set tRequest.Request.RequestFormatCode = "JSON"
	Set tRequest.Request.ResponseFormatCode = tRequest.Request.RequestFormatCode
	Do ..SendRequestSync(..FHIRServerOperation,tRequest, .tResponse)
		
	Q tResponse.Response.Status
}

ClassMethod GetURLFromStream(pQuickStreamId) As %String
{
	Set tRet = ""
	Set tTmpStream = ##class(HS.SDA3.QuickStream).%OpenId(pQuickStreamId)
	if $ISOBJECT(tTmpStream)
	{
		Set tJSON = {}.%FromJSON(tTmpStream)
		If tJSON.total>0
		{
			Set tRet = tJSON.entry.%Get(0).fullUrl
		}
		Kill tJSON
		Do tTmpStream.%Close()
	}
	Q tRet
}

/// find out existing encounter url from fhir repository
Method GetEncounterUrl(pEncounterNo) As %String
{
	Quit:(pEncounterNo="") ""
	
	Set tRequest = ##class(HS.FHIRServer.Interop.Request).%New()
	Set tResponse = ##class(HS.FHIRServer.Interop.Response).%New()
	
	Set tRequest.Request.BaseURL=..FHIRServerEndPoint
	Set tRequest.Request.QueryString="identifier="_pEncounterNo
	Set tRequest.Request.RequestMethod="GET"
	Set tRequest.Request.RequestPath="/Encounter"
	Set tRequest.Request.RequestFormatCode = "JSON"
	Set tRequest.Request.ResponseFormatCode = tRequest.Request.RequestFormatCode
	Do ..SendRequestSync(..FHIRServerOperation,tRequest, .tResponse)
		
	Q ..GetURLFromStream(tResponse.QuickStreamId)
}

/// find out existing patient url from fhir repository
Method GetPatientUrl(pNIDNo) As %String
{
	Quit:(pNIDNo="") ""
		
	Set tRequest = ##class(HS.FHIRServer.Interop.Request).%New()
	Set tResponse = ##class(HS.FHIRServer.Interop.Response).%New()
	
	Set tRequest.Request.BaseURL=..FHIRServerEndPoint
	Set tRequest.Request.QueryString="identifier="_pNIDNo
	Set tRequest.Request.RequestMethod="GET"
	Set tRequest.Request.RequestPath="/Patient"
	Set tRequest.Request.RequestFormatCode = "JSON"
	Set tRequest.Request.ResponseFormatCode = tRequest.Request.RequestFormatCode
	Do ..SendRequestSync(..FHIRServerOperation,tRequest, .tResponse)
		
	Q ..GetURLFromStream(tResponse.QuickStreamId)
}

/// find out existing location url from fhir repository
Method GetLocationUrl(pLocId) As %String
{
	Quit:(pLocId="") ""
		
	Set tRequest = ##class(HS.FHIRServer.Interop.Request).%New()
	Set tResponse = ##class(HS.FHIRServer.Interop.Response).%New()
	
	Set tRequest.Request.BaseURL=..FHIRServerEndPoint
	Set tRequest.Request.QueryString="identifier="_pLocId
	Set tRequest.Request.RequestMethod="GET"
	Set tRequest.Request.RequestPath="/Location"
	Set tRequest.Request.RequestFormatCode = "JSON"
	Set tRequest.Request.ResponseFormatCode = tRequest.Request.RequestFormatCode
	Do ..SendRequestSync(..FHIRServerOperation,tRequest, .tResponse)
		
	Q ..GetURLFromStream(tResponse.QuickStreamId)
}

/// find out existing Organization url from fhir repository
Method GetOrganizationUrl(pOrgId) As %String
{
	Quit:(pOrgId="") ""
	
	Set tRequest = ##class(HS.FHIRServer.Interop.Request).%New()
	Set tResponse = ##class(HS.FHIRServer.Interop.Response).%New()
	
	Set tRequest.Request.BaseURL=..FHIRServerEndPoint
	Set tRequest.Request.QueryString="identifier="_pOrgId
	Set tRequest.Request.RequestMethod="GET"
	Set tRequest.Request.RequestPath="/Organization"
	Set tRequest.Request.RequestFormatCode = "JSON"
	Set tRequest.Request.ResponseFormatCode = tRequest.Request.RequestFormatCode
	Do ..SendRequestSync(..FHIRServerOperation,tRequest, .tResponse)
		
	Q ..GetURLFromStream(tResponse.QuickStreamId)
}

/// find out existing Practitioner url from fhir repository
Method GetPractitionerUrl(pPracId) As %String
{
	Quit:(pPracId="") ""
	
	Set tRequest = ##class(HS.FHIRServer.Interop.Request).%New()
	Set tResponse = ##class(HS.FHIRServer.Interop.Response).%New()
	
	Set tRequest.Request.BaseURL=..FHIRServerEndPoint
	Set tRequest.Request.QueryString="identifier="_pPracId
	Set tRequest.Request.RequestMethod="GET"
	Set tRequest.Request.RequestPath="/Practitioner"
	Set tRequest.Request.RequestFormatCode = "JSON"
	Set tRequest.Request.ResponseFormatCode = tRequest.Request.RequestFormatCode
	Do ..SendRequestSync(..FHIRServerOperation,tRequest, .tResponse)
		
	Q ..GetURLFromStream(tResponse.QuickStreamId)
}

XData MessageMap
{
<MapItems>
	<MapItem MessageType="Ens.StreamContainer"> 
		<Method>ReviseAndUpload</Method>
	</MapItem>
</MapItems>
}

}
